import * as THREE from "three";
import CapsuleBufferGeometry from "../geometry/capsule-buffer-geometry";
import Vec3 from "./common/vec3";

const COMPARE_PAIR=(a,b)=>{
    return (a[0]===b[0]&&a[1]===b[1])||(a[0]===b[1]&&a[1]===b[0]);
};

let ratio=1.01;
let color=new THREE.Color('#ff00ff');
let size=0.4;
let tV0=new Vec3();

export default class PhysicsDebug {
    constructor(){}

    //..................................public.......................................

    static set size(num){
        size=num||0.4;
    }
    static set ratio(num){
        ratio=num||1.01;
    }
    static set color(col){
        color.copy(col);
    }

    static box(min_max_array,scene){
        let obj=new THREE.Object3D();
        for(let i=0;i<min_max_array.length;i+=2){
            let min=min_max_array[i],max=min_max_array[i+1];
            let v0=[min.x,min.y,min.z],v1=[min.x,min.y,max.z],v2=[max.x,min.y,max.z],v3=[max.x,min.y,min.z];
            let v4=[min.x,max.y,min.z],v5=[min.x,max.y,max.z],v6=[max.x,max.y,max.z],v7=[max.x,max.y,min.z];
            let e0=PhysicsDebug._createSegment(...v0,...v1,...v2,...v3,...v0);
            let e1=PhysicsDebug._createSegment(...v4,...v5,...v6,...v7,...v4);
            let s0=PhysicsDebug._createSegment(...v0,...v4);
            let s1=PhysicsDebug._createSegment(...v1,...v5);
            let s2=PhysicsDebug._createSegment(...v2,...v6);
            let s3=PhysicsDebug._createSegment(...v3,...v7);
            obj.add(e0,e1,s0,s1,s2,s3);
        }
        if(scene)scene.add(obj);
        return obj;
    }
    static position(array,scene){
        let g=new THREE.BufferGeometry();
        g.setAttribute('position',new THREE.BufferAttribute(new Float32Array([...array]),3));
        let p=new THREE.Points(g,new THREE.PointsMaterial({
            size:size,
            color:color,
            transparent: true,
            depthFunc: THREE.AlwaysDepth,
        }));
        if(scene)scene.add(p);
        return p;
    }
    static vector(array,scene){
        let obj=new THREE.Object3D();
        for(let i=0;i<array.length;i+=3){
            let l=PhysicsDebug._createSegment(0,0,0,array[i],array[i+1],array[i+2]);
            l.material.transparent=true;
            l.material.depthFunc=THREE.AlwaysDepth;
            obj.add(l);
        }
        if(scene)scene.add(obj);
        return obj;
    }
    static line(array,scene){
        let line=PhysicsDebug._createSegment(...array);
        if(scene)scene.add(line);
        return line;
    }

    static boxShape(shape,scene,rigidBody) {
        let geo = shape.getGeometry();
        let w = geo.halfWidth;
        let h = geo.halfHeight;
        let d = geo.halfDepth;
        let g = new THREE.BoxGeometry(w * 2, h * 2, d * 2);
        return PhysicsDebug._createDebugMesh(g, shape, scene, rigidBody);
    }
    static sphereShape(shape,scene,rigidBody){
        let geo=shape.getGeometry();
        let r=geo.getRadius();
        let g=new THREE.SphereGeometry(r,32,16);
        return PhysicsDebug._createDebugMesh(g,shape,scene,rigidBody);
    }
    static cylinderShape(shape,scene,rigidBody){
        let geo=shape.getGeometry();
        let r=geo.getRadius();
        let h=geo.getHalfHeight()*2;
        let g=new THREE.CylinderGeometry(r,r,h,32);
        return PhysicsDebug._createDebugMesh(g,shape,scene,rigidBody);
    }
    static coneShape(shape,scene,rigidBody){
        let geo=shape.getGeometry();
        let r=geo.getRadius();
        let h=geo.getHalfHeight()*2;
        let g=new THREE.ConeGeometry(r,h,32,1);
        return PhysicsDebug._createDebugMesh(g,shape,scene,rigidBody);
    }
    static capsuleShape(shape,scene,rigidBody){
        let geo=shape.getGeometry();
        let r=geo.getRadius();
        let h=geo.getHalfHeight()*2;
        let g=new CapsuleBufferGeometry(r,h,32,16,1,0,Math.PI*2);
        return PhysicsDebug._createDebugMesh(g,shape,scene,rigidBody);
    }
    static convexShape(shape,scene,rigidBody){
        let geo=shape.getGeometry();
        let ps=geo.getVertices();
        let obj=new THREE.Object3D();
        let rs=[];
        for(let i=0;i<ps.length;i++){
            for(let j=0;j<ps.length;j++){
                if(i===j)continue;
                let c=[i,j];
                if(rs.findIndex((r)=>{return COMPARE_PAIR(r,c);})>-1)continue;
                rs.push(c);
                obj.add(PhysicsDebug._createSegment(ps[i].x,ps[i].y,ps[i].z,ps[j].x,ps[j].y,ps[j].z));
            }
        }
        obj.scale.multiplyScalar(ratio);
        if(scene)scene.add(obj);
        if(rigidBody)rigidBody.addObject3D(obj);
        shape.localTransform.getPositionTo(tV0);
        obj.position.copy(tV0);
        shape.localTransform.getOrientationTo(obj.quaternion);
        return obj;
    }

    //..................................private......................................

    static get _debugMaterial(){
        let material=new THREE.ShaderMaterial();
        material.uniforms={
            color:{value:color},
            size:{value:size}
        };
        material.vertexShader=`
            varying vec2 vuv;
            void main(){
                vec4 pmv=modelViewMatrix*vec4(position,1.);
                gl_Position=projectionMatrix*pmv;
                vuv=uv;
            }
        `;
        material.fragmentShader=`
            uniform vec3 color;
            uniform float size;
            varying vec2 vuv;
            void main(){
                vec2 tuv=vuv*10.;
                if(mod(tuv.x,1.)>size){
                    discard;
                }
                if(mod(tuv.y,1.)>size){
                    discard;
                }
                gl_FragColor=vec4(color,1.);
            }
        `;
        return material;
    }
    static _createDebugMesh(geometry,shape,scene,rigidBody){
        let dg=new THREE.Mesh(geometry,PhysicsDebug._debugMaterial);
        dg.scale.multiplyScalar(ratio);
        if(scene)scene.add(dg);
        if(rigidBody)rigidBody.addObject3D(dg);
        shape.localTransform.getPositionTo(tV0);
        dg.position.copy(tV0);
        shape.localTransform.getOrientationTo(dg.quaternion);
        return dg;
    }
    static _createSegment(...xyz){
        let g=new THREE.BufferGeometry();
        g.setAttribute('position',new THREE.BufferAttribute(new Float32Array(xyz),3));
        return new THREE.Line(g,new THREE.LineBasicMaterial({
            color:color,
        }));
    }

}